package com.ck.print.demo.printbridge;


import com.ck.print.demo.printbridge.message.handler.FinalMessageHandle;
import com.ck.print.demo.printbridge.message.handler.IMessageHandle;
import com.ck.print.demo.printbridge.message.handler.MessageHandleChain;
import com.google.gson.Gson;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

@Slf4j
public class PrintWebSocketClient extends WebSocketClient {

    private final ScheduledExecutorService scheduler;
    private final AtomicBoolean isReconnecting = new AtomicBoolean(false);
    private int retryCount = 0;

    @Getter
    private final String clientName;
    private String mac;

    protected final Gson gson = new Gson();
    protected final Map<PrintWebSocketClient, PrintWebSocketClient> channelMap = new HashMap<>(4);
    protected final MessageHandleChain messageHandleChain;

    public PrintWebSocketClient(String serverUri, String clientName, ScheduledExecutorService scheduler) throws URISyntaxException {
        super(new URI(serverUri));
        this.clientName = clientName;
        this.scheduler = scheduler;
        this.messageHandleChain = new MessageHandleChain();

    }

    /**
     * 初始化通道,并增加消息处理器
     *
     * @param channel        socket
     * @param messageHandler 消息处理器
     */
    @SafeVarargs
    public final void initChannel(PrintWebSocketClient channel, IMessageHandle<Map<String, Object>, Boolean>... messageHandler) {
        channelMap.put(this, channel);
        if (messageHandler != null) {
            for (IMessageHandle<Map<String, Object>, Boolean> mapBooleanIMessageHandle : messageHandler) {
                messageHandleChain.add(mapBooleanIMessageHandle);
            }
        }
        this.messageHandleChain.add(new FinalMessageHandle(clientName));
    }

    @Override
    public void connect() {
        if (channelMap.isEmpty()) {
            throw new RuntimeException("请先初始化通道");
        }
        if (channelMap.get(this).equals(this)) {
            throw new RuntimeException("请勿初始化自己为通道");
        }
        super.connect();
    }

    /**
     * call: callName,
     * promise: { resolve: resolve, reject: reject },
     * params: params,
     * signature: signature,
     * timestamp: signingTimestamp
     *
     * @param handshakedata The handshake of the websocket instance
     */
    @Override
    public void onOpen(ServerHandshake handshakedata) {
        log.info("{}连接成功！状态码：{},消息：{}", clientName, handshakedata.getHttpStatus(), handshakedata.getHttpStatusMessage());
        retryCount = 0; // 重置重试计数
    }

    // 收到消息时调用
    @Override
    public void onMessage(String message) {
        log.info("{}收到消息：{}", clientName, message);
        // 处理业务逻辑（例如解析JSON、更新本地数据等）
        //特殊处理特定指令
        Map<String, Object> dataMap = gson.fromJson(message, Map.class);
        messageHandleChain.handle(dataMap, this, channelMap.get(this));
    }


    // 连接关闭时调用
    @Override
    public void onClose(int code, String reason, boolean remote) {
        log.info("{}连接关闭：code={}, reason:{}", clientName, code, reason);
        // 触发重连（远程主动关闭或本地断连都尝试重连）
        scheduleReconnect();
    }

    // 发生错误时调用
    @Override
    public void onError(Exception ex) {
        log.info("{}连接错误：{}", clientName, ex.getMessage());
        // 错误导致的断连也触发重连
        scheduleReconnect();
    }

    // 调度重连（带指数退避策略）
    private final Random random = new Random();

    private void scheduleReconnect() {
        if (retryCount == Integer.MAX_VALUE) {
            retryCount = 0;
        }
        // 防止并发重连
        if (isReconnecting.compareAndSet(false, true)) {
            // 指数退避：延迟时间 = 初始延迟 * 2^重试次数（最多延迟30秒）
            long delay = random.nextInt(15);

            log.info("{}计划在 {} 秒后重连（第 {} 次）", clientName, delay, retryCount + 1);
            scheduler.schedule(() -> {
                try {
                    retryCount++;
                    close();
                    reconnect(); // 执行重连
                    isReconnecting.set(false);
                } catch (Exception e) {
                    isReconnecting.set(false);
                    scheduleReconnect();
                } finally {

                }
            }, delay, TimeUnit.SECONDS);
        }
    }

    // 关闭资源（程序退出时调用）
    public void shutdown() {
        log.info("{}正在关闭资源...", clientName);
        scheduler.shutdown();
        close();
    }
}
